
// === uuCodec ===
// depend: none
/*
uuCodecDataURI.decode(str) - return { mime, data }
uuCodecHex.decode("%00%01") - return [0x00, 0x01]
uuCodecBase64.encode(ByteArray, safe64) - return Base64String/URLSafe64String
uuCodecBase64.decode(str) - return ByteArray
 */
(function() {
var _datauri,   // inner namespace
    _hex,       // inner namespace
    _base64,    // inner namespace
    _b64code =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
    _b64hash = { "=": 0 };

_datauri = {
  // uuCodecDataURI.decode
  //    ...
  //    data:text/css,.picture%20%7B%20background%3A%20none%3B%20%7D
  decode: function(str) { // @param String:
                          // @return Hash: { mime, data }
    var m, mime, data;

    if ( (m = /^data:([\w\/]+)(;base64)?,/.exec(str)) ) {
      mime = m[1];
      data = str.slice(m[0].length);
      data = m[2] ? _base64.decode(decodeURIComponent(data)) // base64
                  : _hex.decode(data); // decodeURI weak("%00")
    }
    return { mime: mime || "", data: data || [] };
  }
};

_hex = {
  // uuCodecHex.decode - "%00%01" to [0x00, 0x01]
  decode: function(hex) { // @param HexString: "%00" + ASCII string
                          // @return ByteArray:
    var rv = [], ri = -1, c = 0, i = 0, iz = hex.length,
        mark = "%".charCodeAt(0);

    if (!hex.length) {
      return [];
    }
    if (hex.length >= 3) {
      if (hex.charAt(hex.length - 1) === "%" || // tail "...%"
          hex.charAt(hex.length - 2) === "%") { // tail "..%x"
        return []; // bad data
      }
    }
    while (i < iz) {
      c = hex.charCodeAt(i++);
      if (c === mark) {
        rv[++ri] = parseInt(hex.charAt(i) + hex.charAt(i + 1), 16);
        i += 2;
      } else {
        rv[++ri] = c;
      }
    }
    return rv;
  }
};

_base64 = {
  // uuCodecBase64.encode - ByteArray to Base64String
  encode: function(ary,         // @param ByteArray: array( [0x0, ... ] )
                   URLSafe64) { // @param Boolean(= true): true = URLSafe
                                // @return Base64String/URLSafe64String:
    var rv = [], pad = 0, code = _b64code, c = 0, i = 0, iz;

    switch (ary.length % 3) {
    case 1: ary.push(0); ++pad;
    case 2: ary.push(0); ++pad;
    }
    iz = ary.length;

    while (i < iz) {
      c = (ary[i++] << 16) | (ary[i++] << 8) | (ary[i++]);
      rv.push(code.charAt((c >>> 18) & 0x3f),
              code.charAt((c >>> 12) & 0x3f),
              code.charAt((c >>>  6) & 0x3f),
              code.charAt( c         & 0x3f));
    }
    switch (pad) {
    case 2: rv[rv.length - 2] = "=";
    case 1: rv[rv.length - 1] = "=";
    }

    if (URLSafe64 === void 0 || URLSafe64) {
      return rv.join("").replace(/\+/g, "-").
                         replace(/\//g, "_").replace(/=/g, "");
    }
    return rv.join("");
  },

  // uuCodecBase64.decode - Base64String to ByteArray
  decode: function(b64) { // @param Base64String/URLSafe64String:
                          // @return ByteArray:
    if (typeof b64 !== "string" || !b64.length) {
      return []; // empty
    }

    // URLBase64Charcter("-", "_") convert to ("+", "/")
    b64 = b64.replace(/-/g, "+").replace(/_/g, "/").replace(/=/g, "");

    if (/[^A-Za-z0-9\+\/]/.test(b64)) {
      return []; // bad data
    }

    var rv = [], pad = 0, hash = _b64hash, c = 0, i = 0, iz;

    switch (b64.length % 4) { // pad length( "=" or "==" or "" )
    case 2: b64 += "="; ++pad;
    case 3: b64 += "="; ++pad;
    }

    iz = b64.length;
    while (i < iz) {                    // 00000000|00000000|00000000
      c = (hash[b64.charAt(i++)] << 18) // 111111  |        |
        | (hash[b64.charAt(i++)] << 12) //       11|1111    |
        | (hash[b64.charAt(i++)] <<  6) //         |    1111|11
        |  hash[b64.charAt(i++)]        //         |        |  111111
      rv.push((c >>> 16) & 0xff, (c >>> 8) & 0xff, c & 0xff);
    }
    rv.length -= [0,1,2][pad]; // cut tail
    return rv;
  }
};

// --- initialize ---
(function() {
  // make hash
  for (var i = 0, iz = _b64code.length; i < iz; ++i) {
    _b64hash[_b64code.charAt(i)] = i;
  }
})();

// --- export ---
window.uuCodecDataURI = _datauri;   // window.uuCodecDataURI
window.uuCodecHex     = _hex;       // window.uuCodecHex
window.uuCodecBase64  = _base64;    // window.uuCodecBase64

})(); // uuCodec scope
